﻿using gt.rediscachemanager.Core.ClientServer;
using gt.rediscachemanager.Core.Executor;
using gt.rediscachemanager.Entry;
using gt.rediscachemanager.Utility;
using StackExchange.Redis;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace gt.rediscachemanager.Impl.RedisClient
{
    public partial class RedisClient : IRedisClient
    {
        protected RedisClientServerPool m_clientServerPool = null;
        private RedisClientConfiguration m_configuration = null;

        #region Construct

        public RedisClient(RedisClientConfiguration configuration)
        {
            this.m_configuration = configuration ?? throw new ArgumentNullException("cachePool");
            if (string.IsNullOrEmpty(configuration.ClientName) || configuration.Pool == null || string.IsNullOrEmpty(configuration.Pool.Name))
                throw new InvalidOperationException("configuration property value error!");

            this.m_clientServerPool = RedisClientServerPoolFactory.GetOrCreate(configuration.Pool);
        }

        #endregion

        protected virtual async Task<EMRedisResult> ExecuteAsync(RedisCommand command, EMRedisMessage message, CommandFlags commandFlags = CommandFlags.None)
        {
            var clientServer = this.GetClientServer(message.Key);
            return await clientServer.ExecuteCommandAsync(command, message, commandFlags).ConfigureAwait(false);
        }
        protected virtual EMRedisResult Execute(RedisCommand command, EMRedisMessage message, CommandFlags commandFlags = CommandFlags.None)
        {
            var clientServer = this.GetClientServer(message.Key);
            return clientServer.ExecuteCommand(command, message, commandFlags);
        }

        #region Server

        public string AppCacheName { get { return this.m_configuration.ClientName; } }
        public virtual IRedisClient BackupClient { get { return null; } }
        public virtual CachePoolConfiguration Pool { get { return this.m_configuration.Pool; } }
        public RedisClientServer GetClientServerByNodeName(string nodeName)
        {
            return this.m_clientServerPool.GetClientServerByNodeName(nodeName);
        }
        public RedisClientServer[] GetClientServers()
        {
            return this.m_clientServerPool.GetAllRedisClientServer().ToArray();
        }
        public RedisClientServer GetClientServer(string key)
        {
            return this.m_clientServerPool.GetClientServer(key);
        }
        public virtual Tuple<RedisClientServer, bool> GetSmartClientServer(string key)
        {
            return new Tuple<RedisClientServer, bool>(this.GetClientServer(key), false);
        }
        public virtual RedisClientInfo RedisCacheInfo()
        {
            RedisClientInfo info = new RedisClientInfo()
            {
                Version = RedisClientUtility.Version,
                ApplicatioinName = this.m_configuration.ApplicationName,
                ServerIp = RedisClientUtility.GetLocalIp(),
                IDC = this.m_configuration.IDC,
                AppCacheName = this.m_configuration.ClientName,
                ConnectionCache = new RedisConnectCache
                {
                    Name = this.m_configuration.Pool.Name,
                    HasActive = true,
                    Nodes = this.GetClientServers().Select((cs) =>
                    {
                        return new RedisConnectNode
                        {
                            DB = cs.Node.DB,
                            Name = cs.Node.Name,
                            SlotFrom = cs.Node.SlotFrom,
                            SlotTo = cs.Node.SlotTo,
                            Master = cs.Node.Master,
                            Slaves = cs.Node.Slaves,
                            Available = true
                        };
                    }).ToList()
                }
            };
            return info;
        }

        #endregion

        #region Key

        public bool LockTake(string key, string value, TimeSpan expiredTime)
        {
            var message = new LockTakeMessage(key, value, expiredTime);
            return Execute(RedisCommand.LockTake, message, CommandFlags.DemandMaster).BoolResult;
        }
        public string LockQuery(string key)
        {
            var message = new LockQueryMessage(key);
            return Execute(RedisCommand.LockQuery, message, CommandFlags.DemandMaster).RedisValueResult;
        }
        public bool LockRelease(string key, string value)
        {
            var message = new LockReleaseMessage(key, value);
            return Execute(RedisCommand.LockRelease, message, CommandFlags.DemandMaster).BoolResult;
        }
        public RedisInfo RedisInfo(string hostAndport)
        {
            var cs = this.GetClientServers().FirstOrDefault(e => string.Equals(e.Node.Master, hostAndport) || e.Node.Slaves.Contains(hostAndport));
            if (cs == null) return null;
            var infoString = cs.ExecuteCommand(RedisCommand.Info, new RedisInfoMessage(hostAndport), CommandFlags.DemandMaster).InfoResult;
            return RedisParser.FromInfoString(infoString);
        }
        public ConnectClientInfo[] RedisClients(string hostAndport)
        {
            var cs = this.GetClientServers().FirstOrDefault(e => string.Equals(e.Node.Master, hostAndport) || e.Node.Slaves.Contains(hostAndport));
            if (cs == null) return null;
            var clientList = cs.ExecuteCommand(RedisCommand.ClientList, new ClientListMessage(hostAndport), CommandFlags.DemandMaster).ClientInfoArrayResult;
            return RedisParser.FromClientInfo(clientList);
        }

        public bool KeyExists(string key, CommandFlags flags = CommandFlags.None)
        {
            var message = new KeyExistsMessage(key);
            return Execute(RedisCommand.KeyExists, message, flags).BoolResult;
        }

        public bool KeyRemove(string key, CommandFlags flags = CommandFlags.None)
        {
            var message = new KeyRemoveMessage(key);
            return Execute(RedisCommand.KeyRemove, message, flags).BoolResult;
        }

        public long KeyRemove(string[] keys, CommandFlags flags = CommandFlags.None)
        {
            if (this.Pool.Mode != PoolMode.Single) throw new InvalidOperationException("arrary key remove not support in cluster mode!");
            var redisKeys = RedisValueUtility.ConvertToRedisKeyFromString(keys);
            if (redisKeys == null || redisKeys.Length == 0) return 0;
            var message = new KeyRemoveArrayMessage(redisKeys);
            return Execute(RedisCommand.KeyRemoveArray, message, flags).LongResult;
        }

        public bool KeyExpired(string key, TimeSpan expiredTime, CommandFlags flags = CommandFlags.None)
        {
            var message = new KeyExpiredMessage(key, expiredTime);
            return Execute(RedisCommand.KeyExpired, message, flags).BoolResult;
        }

        public TimeSpan? KeyTimeToLive(string key, CommandFlags flags = CommandFlags.None)
        {
            var message = new KeyTimeToLiveMessage(key);
            return Execute(RedisCommand.KeyTimeToLive, message, flags).TimeSpanResult;
        }

        public async Task<bool> KeyExistsAsync(string key, CommandFlags flags = CommandFlags.None)
        {
            var message = new KeyExistsMessage(key);
            return await ExecuteAsync(RedisCommand.KeyExists, message, flags).ContinueWith(t =>
            {
                return t.Result.BoolResult;
            }).ConfigureAwait(false);
        }

        public async Task<bool> KeyRemoveAsync(string key, CommandFlags flags = CommandFlags.None)
        {
            var message = new KeyRemoveMessage(key);
            return await ExecuteAsync(RedisCommand.KeyRemove, message, flags).ContinueWith(t =>
            {
                return t.Result.BoolResult;
            }).ConfigureAwait(false);
        }

        public async Task<bool> KeyExpiredAsync(string key, TimeSpan expiredTime, CommandFlags flags = CommandFlags.None)
        {
            var message = new KeyExpiredMessage(key, expiredTime);
            return await ExecuteAsync(RedisCommand.KeyExpired, message, flags).ContinueWith(t =>
            {
                return t.Result.BoolResult;
            }).ConfigureAwait(false);
        }

        public async Task<TimeSpan?> KeyTimeToLiveAsync(string key, CommandFlags flags = CommandFlags.None)
        {
            var message = new KeyTimeToLiveMessage(key);
            return await ExecuteAsync(RedisCommand.KeyTimeToLive, message, flags).ContinueWith(t =>
            {
                return t.Result.TimeSpanResult;
            }).ConfigureAwait(false);
        }

        #endregion

        #region Dispose

        /// <summary>
        /// 通常 不建议调用。
        /// IRedisClient应当是常驻的，已较少创建连接的消耗
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                m_clientServerPool.Dispose();
            }
        }

        #endregion
    }
}
